/*
 * Copyright 2019-present Open Networking Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.onosproject.ngsdn.tutorial;

import org.onlab.packet.*;
import org.onlab.util.ItemNotFoundException;
import org.onosproject.core.ApplicationId;
import org.onosproject.mastership.MastershipService;
import org.onosproject.net.*;
import org.onosproject.net.config.NetworkConfigService;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.flow.*;
import org.onosproject.net.flow.criteria.PiCriterion;
import org.onosproject.net.host.HostEvent;
import org.onosproject.net.host.HostListener;
import org.onosproject.net.host.HostService;
import org.onosproject.net.link.LinkEvent;
import org.onosproject.net.link.LinkService;
import org.onosproject.net.link.LinkListener;
import org.onosproject.net.pi.model.PiActionId;
import org.onosproject.net.pi.model.PiActionParamId;
import org.onosproject.net.pi.model.PiMatchFieldId;
import org.onosproject.net.pi.runtime.PiAction;
import org.onosproject.net.pi.runtime.PiActionParam;
import org.onosproject.net.topology.TopologyService;
import org.onosproject.ngsdn.tutorial.common.*;
import org.osgi.service.component.annotations.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.stream.Collectors;

import static org.onosproject.ngsdn.tutorial.AppConstants.*;

/**
 * App component that configures devices to provide L3 Routing capabilities.
 */
@Component(
        immediate = true,
        enabled = true,
        service = RouterComponent.class
)
public class RouterComponent {

    private final Logger log = LoggerFactory.getLogger(getClass());

    private ApplicationId appId;
    private SHAUtil trans = new SHAUtil();
    private Set<Host> hosts = new HashSet<>();  // 保存当前网络中的主机
    private Set<HostToHosLinks> hostToHostLinks= new HashSet<>();   // 保存主机对之间的路径，如果是同一台设备上的不同网段主机，则path=null

    public static HashMap<String, Long> IpToMf = new HashMap<>();
    public static HashMap<String, Long> IpToGeo = new HashMap<>();
    public static HashMap<String, Long> IpToNdn = new HashMap<>();
    private String currentModal = "IP";
    private String currentModal1 = "";

    public boolean isEncrypted() {
        return encrypted;
    }

    public void setEncrypted(boolean encrypted) {
        this.encrypted = encrypted;
    }

    public boolean encrypted;

    //--------------------------------------------------------------------------
    // ONOS CORE SERVICE BINDING
    //
    // These variables are set by the Karaf runtime environment before calling
    // the activate() method.
    //--------------------------------------------------------------------------

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    private HostService hostService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    private DeviceService deviceService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    private NetworkConfigService configService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    private FlowRuleService flowRuleService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    private MastershipService mastershipService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    private MainComponent mainComponent;

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    protected TopologyService topologyService;

    @Reference(cardinality = ReferenceCardinality.MANDATORY)
    private LinkService linkService;

    private final HostListener hostListener = new InternalHostListener();
    private final LinkListener linkListener = new InternalLinkListener();

    //--------------------------------------------------------------------------
    // COMPONENT ACTIVATION.
    //
    // When loading/unloading the app the Karaf runtime environment will call
    // activate()/deactivate().
    //--------------------------------------------------------------------------

    @Activate
    protected void activate() {
        appId = mainComponent.getAppId();

        hostService.addListener(hostListener);
        linkService.addListener(linkListener);
        // Schedule set up of existing devices. Needed when reloading the app.
        mainComponent.scheduleTask(this::setUpAllDevices, INITIAL_SETUP_DELAY);

        log.info("RouterComponent Started");
    }

    @Deactivate
    protected void deactivate() {
        hostService.removeListener(hostListener);
        linkService.removeListener(linkListener);

        log.info("RouterComponent Stopped");
    }

    private void insertHostToHostIntent(Host newHost){
        hosts.forEach(host -> {
            creatHostToHostPath(newHost,host);
            log.info("intent: {},{} ...", host.id(),newHost.id());
        });
    }

    // 创建主机到主机的路径
    // 添加IP流表
    private void creatHostToHostPath(Host srcHost, Host dstHost){
        log.info("create {},{}", srcHost.id(),dstHost.id());

        DeviceId srcDeviceId = srcHost.location().deviceId();
        DeviceId dstDeviceId = dstHost.location().deviceId();

        // 判断源目的主机是否在同一台设备上
        if (srcDeviceId.equals(dstDeviceId)) {
            createSameDeviceFlowRule(srcHost, dstHost);
            hostToHostLinks.add(new HostToHosLinks(srcHost, dstHost, null));
            return;
        }
        // 获取主机对之间的路径
        Path path = getHostToHostPath(srcHost, dstHost, null);

        HostToHosLinks hhLinks = new HostToHosLinks(srcHost, dstHost, path);
        if(hostToHostLinks.contains(hhLinks)){
            for(HostToHosLinks oldHhLinks:hostToHostLinks){
                if(oldHhLinks.equals(hhLinks)){
                    oldHhLinks.setPath(path);
                }
            }
        }else{
            hostToHostLinks.add(hhLinks);
        }
        //插入流表
        //insertFlowRules(hhLinks, "IP");
    }

    // 创建同一个设备上的两台主机之间的流表
    private void createSameDeviceFlowRule(Host srcHost, Host dstHost) {
        log.info("{} and {} connect to the same switch", srcHost.mac(), dstHost.mac());

        DeviceId srcDeviceId = srcHost.location().deviceId();
        DeviceId dstDeviceId = dstHost.location().deviceId();

        String srcIP = srcHost.ipAddresses().stream().findFirst().get().toString();
        String dstIP = dstHost.ipAddresses().stream().findFirst().get().toString();
        long srcPort = srcHost.location().port().toLong();
        long dstPort = dstHost.location().port().toLong();

        FlowRule srcRule = ModalComponent.buildEdgeIPForwardRule(srcDeviceId, srcIP, srcHost.mac(), srcPort);
        FlowRule dstRule = ModalComponent.buildEdgeIPForwardRule(dstDeviceId, dstIP, dstHost.mac(), dstPort);

        flowRuleService.applyFlowRules(srcRule, dstRule);
        return;
    }

    // 获取主机对之间的路径，并可以排除指定路径excludeLink（当链路失效时，需要排除指定链路）
    private Path getHostToHostPath(Host srcHost, Host dstHost, Link excludeLink) {
        DeviceId srcDeviceId = srcHost.location().deviceId();
        DeviceId dstDeviceId = dstHost.location().deviceId();

        Set<Path> paths = topologyService.getPaths(topologyService.currentTopology(), srcDeviceId, dstDeviceId);
        if (paths.isEmpty()) {
            log.info("not get path:{},{}",srcDeviceId,dstDeviceId);
            return null;
        } else {
            log.info("get path:{},{},{}",srcDeviceId,dstDeviceId, paths.toString());
        }

        Path path = null;
        if (excludeLink != null) {
            for (Path p : paths) {
                if (!p.links().contains(excludeLink)) {
                    path = p;
                }
            }
        } else {
            path = paths.stream().findFirst().get();
        }

        return path;
    }

    private Path getAutherHostToHostPath(Host srcHost, Host dstHost, Path excludePath) {
        DeviceId srcDeviceId = srcHost.location().deviceId();
        DeviceId dstDeviceId = dstHost.location().deviceId();
        Set<Path> paths = topologyService.getPaths(topologyService.currentTopology(), srcDeviceId, dstDeviceId);
        if (paths.isEmpty()) {
            log.info("not get path:{},{}",srcDeviceId,dstDeviceId);
            return null;
        } else {
            log.info("get path:{},{},{}",srcDeviceId,dstDeviceId, paths.toString());
        }

        Path path = null;
        if (excludePath != null) {
            for (Path p : paths) {
                if (!p.equals(excludePath)) {
                    path = p;
                }
            }
        } else {
            path = paths.stream().findFirst().get();
        }

        return path;
    }
    // 链路失效时，更新主机间的路径，排除指定路径excludeLink
    private void updateHostToHostPath(Host srcHost, Host dstHost, Link excludeLink){
        log.info("uplink {},{}", srcHost.id(),dstHost.id());

        HostToHosLinks hhLink = hostToHostLinks.stream()
                .filter(hhLinks -> hhLinks.equals(new HostToHosLinks(srcHost,dstHost)))
                .collect(Collectors.toList())
                .get(0);

        if(hhLink == null){
            log.info("*** not get host to host links:{}", hhLink);
        } else {
            log.info("*** get host to host links:{}", hhLink);
        }
        // 获取新路径
        Path newPath = getHostToHostPath(srcHost, dstHost, excludeLink);
        while(newPath == null || newPath.equals(hhLink.getPath())) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            newPath = getHostToHostPath(srcHost, dstHost, excludeLink);
        }

        List<Link> oldLinks = new ArrayList<>();
        List<Link> newLinks = new ArrayList<>();
        if (newPath != null) {
            // 新路径存在时，找到旧的不需要的链路和新添加的链路，根据这些链路修改设备流表
            // hhLink中的path是旧路径
            log.info("Get new path: {}", newPath.links().toString());
            for (Link link : hhLink.getPath().links()) {
                if (!newPath.links().contains(link)) {
                    oldLinks.add(link);
                }
            }
            for (Link link : newPath.links()) {
                if (!hhLink.getPath().links().contains(link)) {
                    newLinks.add(link);
                }
            }
            // 设置新路径
            for(HostToHosLinks hhLinks:hostToHostLinks){
                if(hhLinks.equals(new HostToHosLinks(srcHost,dstHost))){
                    hhLinks.setPath(newPath);
                }
            }
        } else {
            // 没有找到新路径，删除原来的流表
            log.info("No new path");
            oldLinks = hhLink.getPath().links();
        }
        log.info("old links {}", oldLinks.toString());
        log.info("new links {}", newLinks.toString());
        ModifyFlowRule(srcHost, dstHost, oldLinks, newLinks);
    }

    // 根据链路更改设备流表，支持MF和IP模态
    private void ModifyFlowRule(Host srcHost, Host dstHost, List<Link> oldLinks, List<Link> newLinks) {
        log.info("link removed, modify Flow Rule");
        byte a = 0;
        // 删除旧流表
        for (Link link : oldLinks) {
            List<FlowRule> rules = buildLinkFlowRule(srcHost, dstHost, link, currentModal,false,a);
            //flowRuleService.removeFlowRules(rules.toArray(new FlowRule[0]));
            for (var rule: rules) {
                log.info("removing {}, {}, {}, {}", rule.deviceId().toString(), rule.table().toString(),
                        rule.selector().criteria().toString(), rule.treatment().immediate().toString());
                flowRuleService.removeFlowRules(rule);
//                log.info("remove done");
            }
        }
        // 添加新流表
        for (Link link : newLinks) {
            List<FlowRule> rules = buildLinkFlowRule(srcHost, dstHost, link, currentModal,false,a);
            for (var rule : rules) {
                log.info("inserting {}, {}, {}, {}", rule.deviceId().toString(), rule.table().toString(),
                        rule.selector().criteria().toString(), rule.treatment().immediate().toString());
                flowRuleService.applyFlowRules(rule);
//                log.info("insert done");
            }
        }
    }

    private List<FlowRule> buildLinkFlowRule(Host srcHost, Host dstHost, Link link ,String modal,boolean has_mod_result,byte mod_result) {
        log.info("*******Add table entries buildLinkFlowRule *******");
        List<FlowRule> rules = new ArrayList<>();

        String srcIP = srcHost.ipAddresses().stream().findFirst().get().toString();
        String dstIP = dstHost.ipAddresses().stream().findFirst().get().toString();
        Long srcHostPort = srcHost.location().port().toLong();
        Long dstHostPort = dstHost.location().port().toLong();
        DeviceId srcDev = link.src().deviceId();
        DeviceId dstDev = link.dst().deviceId();
        Long srcPort = link.src().port().toLong();
        Long dstPort = link.dst().port().toLong();
        MacAddress srcMac = srcHost.mac();
        MacAddress dstMac = dstHost.mac();

        if (encrypted){
            rules = buildEncryptModelLinkRule(srcDev, dstDev, srcIP, dstIP,
                    srcHostPort, dstHostPort, srcPort, dstPort, srcMac, dstMac, modal,has_mod_result,mod_result);
        }else{
            rules = buildModelLinkRule(srcDev, dstDev, srcIP, dstIP,
                    srcHostPort, dstHostPort, srcPort, dstPort, srcMac, dstMac, modal,has_mod_result,mod_result);
        }

        return rules;
    }
    private List<FlowRule> buildEncryptModelLinkRule(DeviceId srcDev, DeviceId dstDev,
                                              String srcIP, String dstIP,
                                              Long srcHostPort, Long dstHostPort,
                                              Long srcPort, Long dstPort,
                                              MacAddress srcMac, MacAddress dstMac, String modal,boolean has_mod_result,byte mod_result) {

        List<FlowRule> rules = new ArrayList<>();
        log.info("*******Add Encrypt table {} entries build{}LinkRule *******",modal,modal);
        if (isEdge(srcDev)) {
            // Edge device. insert IP2Anymodel and Anymodel2IP flow rule
            if(has_mod_result){
                FlowRule srcIpPollingRule = ModalComponent.buildPollingIPForwardRule(srcDev,srcIP,dstIP);
                FlowRule srcStatefulMFRule = EncryptComponent.buildStatefulForward2AnyModelRule(srcDev,srcIP,dstIP,mod_result, srcPort,modal);
                rules.add(srcIpPollingRule);
                rules.add(srcStatefulMFRule);
            }else{
                FlowRule srcIP2ModelRule = EncryptComponent.buildIpEncryptFlowRule(srcDev, srcIP, dstIP, srcPort,modal);
                rules.add(srcIP2ModelRule);
            }
            FlowRule srcModel2IPRule = EncryptComponent.buildAnyModel2IpDecryptFlowRule(srcDev, dstIP, srcIP,modal);
            FlowRule srcRule = ModalComponent.buildEdgeIPForwardRule(srcDev, srcIP, srcMac, srcHostPort);
            FlowRule srcModelForwardRule = ModalComponent.buildAnyModelForwardRule(srcDev, dstIP, srcPort,modal);
            rules.add(srcRule);
            rules.add(srcModel2IPRule);
            rules.add(srcModelForwardRule);
        } else {
            FlowRule srcModelForwardRule = ModalComponent.buildAnyModelForwardRule(srcDev, dstIP, srcPort,modal);
            rules.add(srcModelForwardRule);
        }

        if (isEdge(dstDev)) {
            if (has_mod_result){
                FlowRule dstIpPollingRule = ModalComponent.buildPollingIPForwardRule(dstDev,dstIP,srcIP);
                FlowRule dstStatefulMFRule = EncryptComponent.buildStatefulForward2AnyModelRule(dstDev,dstIP,srcIP,mod_result, dstPort,modal);
                rules.add(dstIpPollingRule);
                rules.add(dstStatefulMFRule);
            }else{
                FlowRule dstIP2ModelRule = EncryptComponent.buildIpEncryptFlowRule(dstDev, dstIP, srcIP, dstPort,modal);
                rules.add(dstIP2ModelRule);
            }
            // Edge device. insert IP2Anymodel and Anymodel2IP flow rule

            FlowRule dstModel2IPRule = EncryptComponent.buildAnyModel2IpDecryptFlowRule(dstDev, srcIP, dstIP,modal);
            FlowRule dstRule = ModalComponent.buildEdgeIPForwardRule(dstDev, dstIP, dstMac, dstHostPort);
            FlowRule dstModelForwardRule = ModalComponent.buildAnyModelForwardRule(dstDev, srcIP, dstPort,modal);
            rules.add(dstModel2IPRule);
            rules.add(dstRule);
            rules.add(dstModelForwardRule);
        } else {
            FlowRule dstModelForwardRule = ModalComponent.buildAnyModelForwardRule(dstDev, srcIP, dstPort,modal);
            rules.add(dstModelForwardRule);
        }
        return rules;
    }
    private List<FlowRule> buildModelLinkRule(DeviceId srcDev, DeviceId dstDev,
                                           String srcIP, String dstIP,
                                           Long srcHostPort, Long dstHostPort,
                                           Long srcPort, Long dstPort,
                                           MacAddress srcMac, MacAddress dstMac, String modal,boolean has_mod_result,byte mod_result) {

        List<FlowRule> rules = new ArrayList<>();
        log.info("*******Add table MF entries1 buildMFLinkRule *******");
        if (isEdge(srcDev)) {
            // Edge device. insert IP2Anymodel and Anymodel2IP flow rule
            if(has_mod_result){
                FlowRule srcIpPollingRule = ModalComponent.buildPollingIPForwardRule(srcDev,srcIP,dstIP);
                FlowRule srcStatefulMFRule = ModalComponent.buildStatefulForward2AnyModelRule(srcDev,srcIP,dstIP,mod_result, srcPort,modal);
                rules.add(srcIpPollingRule);
                rules.add(srcStatefulMFRule);
            }else{
                FlowRule srcIP2ModelRule = ModalComponent.buildIP2AnyModelRule(srcDev, srcIP, dstIP, srcPort,modal);
                rules.add(srcIP2ModelRule);
            }
            FlowRule srcModel2IPRule = ModalComponent.buildAnyModel2IPRule(srcDev, dstIP, srcIP, srcMac, srcHostPort,modal);
            FlowRule srcModelForwardRule = ModalComponent.buildAnyModelForwardRule(srcDev, dstIP, srcPort,modal);
            rules.add(srcModel2IPRule);
            rules.add(srcModelForwardRule);
        } else {
            FlowRule srcModelForwardRule = ModalComponent.buildAnyModelForwardRule(srcDev, dstIP, srcPort,modal);
            rules.add(srcModelForwardRule);
        }

        if (isEdge(dstDev)) {
            if (has_mod_result){
                FlowRule dstIpPollingRule = ModalComponent.buildPollingIPForwardRule(dstDev,dstIP,srcIP);
                FlowRule dstStatefulMFRule = ModalComponent.buildStatefulForward2AnyModelRule(dstDev,dstIP,srcIP,mod_result, dstPort,modal);
                rules.add(dstIpPollingRule);
                rules.add(dstStatefulMFRule);
            }else{
                FlowRule dstIP2ModelRule = ModalComponent.buildIP2AnyModelRule(dstDev, dstIP, srcIP, dstPort,modal);
                rules.add(dstIP2ModelRule);
            }
            // Edge device. insert IP2Anymodel and Anymodel2IP flow rule

            FlowRule dstModel2IPRule = ModalComponent.buildAnyModel2IPRule(dstDev, srcIP, dstIP, dstMac, dstHostPort,modal);
            FlowRule dstModelForwardRule = ModalComponent.buildAnyModelForwardRule(dstDev, srcIP, dstPort,modal);
            rules.add(dstModel2IPRule);
            rules.add(dstModelForwardRule);
        } else {
            FlowRule dstModelForwardRule = ModalComponent.buildAnyModelForwardRule(dstDev, srcIP, dstPort,modal);
            rules.add(dstModelForwardRule);
        }
        return rules;
    }


    /**
     * Returns the FabricDeviceConfig config object for the given device.
     *
     * @param deviceId the device ID
     * @return FabricDeviceConfig device config
     */
    private Optional<FabricDeviceConfig> getDeviceConfig(DeviceId deviceId) {
        FabricDeviceConfig config = configService.getConfig(
                deviceId, FabricDeviceConfig.class);
        return Optional.ofNullable(config);
    }

    /**
     * Returns true if the given device has isSpine flag set to true in the
     * config, false otherwise.
     *
     * @param deviceId the device ID
     * @return true if the device is a edge, false otherwise
     */
    public boolean isEdge(DeviceId deviceId) {
        return getDeviceConfig(deviceId).map(FabricDeviceConfig::isEdgeRoute)
                .orElseThrow(() -> new ItemNotFoundException(
                        "Missing isSpine config for " + deviceId));
    }

    public String getCurrentModal() {
        log.info("current modal is {}", currentModal);
        return currentModal;
    }

    /**
     * Convert the host ip address to mf guid and store it to hashmap IpToMf.
     *
     * @param host the host
     */
    private void idConversion(Host host) {
        Set<IpAddress> ipAddrSet= host.ipAddresses();
        String ipAddress = host.ipAddresses().stream().findFirst().get().toString();
        if (IpToMf.containsKey(ipAddress)) {
            log.warn("Ip address {} has already exist.", ipAddress);
            return;
        }
        long mf = trans.IPAddressToLong(ipAddress);
        long ndn = trans.IPToLat(ipAddress);
        long geo = trans.IPToLon(ipAddress);
        IpToMf.put(ipAddress, mf);
        IpToGeo.put(ipAddress,geo);
        IpToNdn.put(ipAddress,ndn);
    }

    public class ReturnMessage {
        private String message;
        private boolean status;
        public ReturnMessage(String message, boolean status) {
            this.message = message;
            this.status = status;
        }
        public void setMessage(String message) {
            this.message = message;
        }
        public void setStatus(boolean status) {
            this.status = status;
        }
        public String getMessage() {
            return message;
        }
        public boolean getStatus() {
            return status;
        }
    }
    public ReturnMessage creatSecretTransmissionOneLink(String srcIP,String dstIP,String modal){
        log.info("create a secret transmission link from {} to {} in {}", srcIP,dstIP,modal);
        ReturnMessage returnMessage = new ReturnMessage("",true);

        HostToHosLinks hhlink = null;
        for (HostToHosLinks hlink : hostToHostLinks) {
            Host srcHost = hlink.getSrcHost();
            Host dstHost = hlink.getDstHost();
            String srcIP1 = srcHost.ipAddresses().stream().findFirst().get().toString();
            String dstIP1 = dstHost.ipAddresses().stream().findFirst().get().toString();
            if (srcIP1.equals(srcIP) && dstIP1.equals(dstIP)){
                hhlink = hlink;
                log.info("a secret transmission link from {} to {}", srcIP1,dstIP1);
                break;
            }else if (srcIP1.equals(dstIP) && dstIP1.equals(srcIP)){
                hhlink = hlink;
                log.info("a secret transmission link from {} to {}", srcIP1,dstIP1);
                break;
            }else {
                continue;
            }

        }
        log.info("get path:{},{},{}",srcIP,dstIP, hhlink.getPath().toString());
        currentModal = modal.toUpperCase();
        byte a = 0;
        switch (modal.toUpperCase()) {
            case "MF": {
                log.info("*******Add MF table entries*******");
                insertFlowRules(hhlink,"MF",false,a);
                break;
            }
            case "GEO": {
                log.info("*******Add Geo table entries*******");

                insertFlowRules(hhlink,"GEO",false,a);
                break;
            }
            case "NDN": {
                log.info("*******Add NDN table entries*******");
                insertFlowRules(hhlink,"NDN",false,a);
                break;
            }
        }
        returnMessage.setMessage("The new modal is changes to " + currentModal);
        returnMessage.setStatus(true);
        return returnMessage;
    }
    public ReturnMessage setDeviceFlow(DerviceFlowLink derviceFlowLink){
        ReturnMessage returnMessage = new ReturnMessage("",true);
        log.info("start to insert deviceflows");
        insertDeviceFlows(derviceFlowLink);
        returnMessage.setMessage("");
        returnMessage.setStatus(true);
        return returnMessage;
    }

    public ReturnMessage setAllDeviceFlow(List<DerviceFlowLink> flowLinks){
        ReturnMessage returnMessage = new ReturnMessage("",true);
        log.info("start to insert all deviceflows");
        for(int i = 0; i < flowLinks.size(); i++){
            insertDeviceFlows(flowLinks.get(i));
        }
        returnMessage.setMessage("" );
        returnMessage.setStatus(true);
        return returnMessage;
    }
    public ReturnMessage creatSecretTransmissionManyLink(String srcIP,String dstIP,String modal1,String modal2){
        log.info("create a secret transmission link from {} to {} in {} and {}", srcIP,dstIP,modal1,modal2);
        ReturnMessage returnMessage = new ReturnMessage("",true);

        HostToHosLinks hhlink = null;
        HostToHosLinks hhLink1 = null;
        for (HostToHosLinks hlink : hostToHostLinks) {
            Host srcHost = hlink.getSrcHost();
            Host dstHost = hlink.getDstHost();
            String srcIP1 = srcHost.ipAddresses().stream().findFirst().get().toString();
            String dstIP1 = dstHost.ipAddresses().stream().findFirst().get().toString();
            log.info("a secret transmission link from {} to {}", srcIP1,dstIP1);
            if (srcIP1.equals(srcIP) && dstIP1.equals(dstIP)){
                hhlink = hlink;
                break;
            }else if (srcIP1.equals(dstIP) && dstIP1.equals(srcIP)){
                hhlink = hlink;
                break;
            }else {
                continue;
            }
        }
        if (hhlink == null){
            returnMessage.setMessage("The secret transmission link is null");
            returnMessage.setStatus(false);
        }else {
            Path path = getAutherHostToHostPath(hhlink.getSrcHost(),hhlink.getDstHost(),hhlink.getPath());
            if (path == null){
                hhLink1 = hhlink;
            }else{
                log.info("get path:{}", path.toString());
                hhLink1 = new HostToHosLinks(hhlink.getSrcHost(), hhlink.getDstHost(), path);
            }
            currentModal = modal1.toUpperCase();
            currentModal1 = modal2.toUpperCase();
            byte a = 1;
            byte b = 0 ;
            switch (modal1.toUpperCase()) {
                case "MF": {
                    log.info("*******Add MF table entries*******");
                    insertFlowRules(hhlink,"MF",true,b);
                    break;
                }
                case "GEO": {
                    log.info("*******Add Geo table entries*******");

                    insertFlowRules(hhlink,"GEO",true,b);
                    break;
                }
                case "NDN": {
                    log.info("*******Add NDN table entries*******");
                    insertFlowRules(hhlink,"NDN",true,b);
                    break;
                }
            }
            switch (modal2.toUpperCase()) {
                case "MF": {
                    log.info("*******Add MF table entries*******");
                    insertFlowRules(hhLink1,"MF",true,a);
                    break;
                }
                case "GEO": {
                    log.info("*******Add Geo table entries*******");

                    insertFlowRules(hhLink1,"GEO",true,a);
                    break;
                }
                case "NDN": {
                    log.info("*******Add NDN table entries*******");
                    insertFlowRules(hhLink1,"NDN",true,a);
                    break;
                }
            }
            returnMessage.setMessage("The new modal is changes to " + currentModal);
            returnMessage.setStatus(true);
        }
        return returnMessage;
    }
    public ReturnMessage deleteSecretTransmissionLink(String srcIP,String dstIP){
        log.info("delete a secret transmission link from {} to {}", srcIP,dstIP);
        ReturnMessage returnMessage = new ReturnMessage("",true);

        String lastModal = currentModal;
        String lastModal1 = currentModal1;
        switch (lastModal.toUpperCase()) {
            case "MF": {
                log.info("*******Delete MF table entries*******");
                deviceService.getAvailableDevices().
                        forEach(device -> delMFForwardRule(device.id()));
                break;
            }
            case "GEO": {
                log.info("*******Delete Geo table entries*******");

                deviceService.getAvailableDevices().
                        forEach(device -> delGeoForwardRule(device.id()));
                break;
            }
            case "NDN": {
                log.info("*******Delete NDN table entries*******");
                deviceService.getAvailableDevices().
                        forEach(device -> delNdnForwardRule(device.id()));
                break;
            }
        }
        switch (lastModal1.toUpperCase()) {
            case "MF": {
                log.info("*******Delete MF table entries*******");
                deviceService.getAvailableDevices().
                        forEach(device -> delMFForwardRule(device.id()));
                break;
            }
            case "GEO": {
                log.info("*******Delete Geo table entries*******");

                deviceService.getAvailableDevices().
                        forEach(device -> delGeoForwardRule(device.id()));
                break;
            }
            case "NDN": {
                log.info("*******Delete NDN table entries*******");
                deviceService.getAvailableDevices().
                        forEach(device -> delNdnForwardRule(device.id()));
                break;
            }
        }
        returnMessage.setMessage("The secret transmission link is deleted ");
        returnMessage.setStatus(true);
        return returnMessage;
    }

    public ReturnMessage deleteAllModelRule(){
        log.info("delete all moadl rule");
        ReturnMessage returnMessage = new ReturnMessage("",true);

        String lastModal = currentModal;
        String lastModal1 = currentModal1;
        switch (lastModal.toUpperCase()) {
            case "MF": {
                log.info("*******Delete MF table entries*******");
                deviceService.getAvailableDevices().
                        forEach(device -> delMFForwardRule(device.id()));
                break;
            }
            case "GEO": {
                log.info("*******Delete Geo table entries*******");

                deviceService.getAvailableDevices().
                        forEach(device -> delGeoForwardRule(device.id()));
                break;
            }
            case "NDN": {
                log.info("*******Delete NDN table entries*******");
                deviceService.getAvailableDevices().
                        forEach(device -> delNdnForwardRule(device.id()));
                break;
            }case "IP": {
                log.info("*******Delete IP table entries*******");
                deviceService.getAvailableDevices().
                        forEach(device -> delIPForwardRule(device.id()));
                break;
            }
        }
        switch (lastModal1.toUpperCase()) {
            case "MF": {
                log.info("*******Delete MF table entries*******");
                deviceService.getAvailableDevices().
                        forEach(device -> delMFForwardRule(device.id()));
                break;
            }
            case "GEO": {
                log.info("*******Delete Geo table entries*******");

                deviceService.getAvailableDevices().
                        forEach(device -> delGeoForwardRule(device.id()));
                break;
            }
            case "NDN": {
                log.info("*******Delete NDN table entries*******");
                deviceService.getAvailableDevices().
                        forEach(device -> delNdnForwardRule(device.id()));
                break;
            }
        }
        returnMessage.setMessage("The secret transmission link is deleted ");
        returnMessage.setStatus(true);
        return returnMessage;
    }

    public ReturnMessage deleteAllRule(){
        log.info("delete all rule");
        ReturnMessage returnMessage = new ReturnMessage("",true);
        log.info("*******Delete MF table entries*******");
        deviceService.getAvailableDevices().
                forEach(device -> delMFForwardRule(device.id()));
        log.info("*******Delete Geo table entries*******");
        deviceService.getAvailableDevices().
                forEach(device -> delGeoForwardRule(device.id()));
        log.info("*******Delete NDN table entries*******");
        deviceService.getAvailableDevices().
                forEach(device -> delNdnForwardRule(device.id()));
        log.info("*******Delete IP table entries*******");
        deviceService.getAvailableDevices().
                forEach(device -> delIPForwardRule(device.id()));

        returnMessage.setMessage("The secret transmission link is deleted ");
        returnMessage.setStatus(true);
        return returnMessage;
    }
    public ReturnMessage switchModal(String modal) {
        log.info("Changing to new modal {}", modal);
        ReturnMessage returnMessage = new ReturnMessage("",true);

        currentModal = modal.toUpperCase();
        byte a = 0;
        switch (modal.toUpperCase()) {
            case "MF": {
                log.info("*******Add MF table entries*******");
                for (HostToHosLinks hlink : hostToHostLinks) {
                    insertFlowRules(hlink,"MF",false,a);
                }
                break;
            }
            case "GEO": {
                log.info("*******Add Geo table entries*******");
                for (HostToHosLinks hlink : hostToHostLinks) {
                    insertFlowRules(hlink,"GEO",false,a);
                }
                break;
            }
            case "NDN": {
                log.info("*******Add NDN table entries*******");
                for (HostToHosLinks hlink : hostToHostLinks) {
                    insertFlowRules(hlink,"NDN",false,a);
                }
                break;
            }
            default: {
                log.warn("The new modal is Unsupported. Nothing changes");
                returnMessage.setMessage("The new modal is Unsupported. Nothing changes");
                returnMessage.setStatus(false);
                return returnMessage;
            }
        }
        returnMessage.setMessage("The new modal is changes to " + currentModal);
        returnMessage.setStatus(true);
        return returnMessage;
    }
    public ReturnMessage switchManyModal() {
//        log.info("Changing to new modal {}", modal);
        ReturnMessage returnMessage = new ReturnMessage("",true);
        Random numList = new Random();
        byte a = 0;
        for (HostToHosLinks hlink : hostToHostLinks) {
            int num = numList.nextInt(3);
            switch (num){
                case 0 : {
                    log.info("*******random is 0*******");
                    currentModal = "MF";
                    break;
                }
                case 1 : {
                    log.info("*******random is 1*******");
                    currentModal = "GEO";
                    break;
                }
                case 2 : {
                    log.info("*******random is 2*******");
                    currentModal = "NDN";
                    break;
                }
                default: {
                    log.warn("The random is",num);
                    currentModal = "MF";
                    break;
                }
            }
            switch (currentModal) {
                case "MF": {
                    log.info("*******Add MF table entries*******");
                    insertFlowRules(hlink,"MF",false,a);
                    break;
                }
                case "GEO": {
                    log.info("*******Add Geo table entries*******");
                    insertFlowRules(hlink,"GEO",false,a);
                    break;
                }
                case "NDN": {
                    log.info("*******Add NDN table entries*******");
                    insertFlowRules(hlink,"NDN",false,a);
                    break;
                }
                default: {
                    log.warn("The new modal is Unsupported. Nothing changes");
                    returnMessage.setMessage("The new modal is Unsupported. Nothing changes");
                    returnMessage.setStatus(false);
                    return returnMessage;
                }
            }
        }
//        currentModal = modal.toUpperCase();


        returnMessage.setMessage("The new modal is changes to " + currentModal);
        returnMessage.setStatus(true);
        return returnMessage;
    }

    private void insertDeviceFlows(DerviceFlowLink derviceFlowLink){

        List<FlowRule> rules = new ArrayList<>();
        for (FlowLink flowlink :derviceFlowLink.getFlowLinks()){
            FlowRule dstIpflowrule = ModalComponent.buildCoreIPForwardRule(derviceFlowLink.getDeviceId(),flowlink.getDstIP(),flowlink.getPost());
            log.info("Added flows in {} from {} to {}",derviceFlowLink.getDeviceId(),flowlink.getSrcIP(),flowlink.getDstIP());
            rules.add(dstIpflowrule);
        }
        flowRuleService.applyFlowRules(rules.toArray(new FlowRule[0]));

    }
    // 根据模态创建流表
    private void insertFlowRules(HostToHosLinks hLinks, String modal,boolean has_mod_result,byte mod_result) {
        Host srcHost = hLinks.getSrcHost();
        Host dstHost = hLinks.getDstHost();

        DeviceId srcDeviceId = srcHost.location().deviceId();
        DeviceId dstDeviceId = dstHost.location().deviceId();
        // 对于同一台设备上的主机，只能通过IP模态通信，直接设置出端口和mac，无法也不需要转换模态
        if (srcDeviceId.equals(dstDeviceId)) {
            createSameDeviceFlowRule(srcHost, dstHost);
            return;
        }

        for (Link link : hLinks.getPath().links()) {
            List<FlowRule> rules = buildLinkFlowRule(srcHost, dstHost, link, modal,has_mod_result,mod_result);
            flowRuleService.applyFlowRules(rules.toArray(new FlowRule[0]));
        }
    }

    public void delMFForwardRule(DeviceId deviceId) {
        final String mfForwardTableId = "IngressPipeImpl.mf_forward_table";
        final String mfConvTableId = "IngressPipeImpl.mf_conversion_table";

        delIPForwardRule(deviceId);
        delIPConversionRule(deviceId);
        delIPPollingForwardRule(deviceId);

        delFlowRuleByTableId(deviceId, mfForwardTableId);
        delFlowRuleByTableId(deviceId, mfConvTableId);
    }

    public void delGeoForwardRule(DeviceId deviceId) {
        final String geoForwardTableId = "IngressPipeImpl.geo_forward_table";
        final String geoConvTableId = "IngressPipeImpl.geo_conversion_table";

        delIPForwardRule(deviceId);
        delIPConversionRule(deviceId);
        delIPPollingForwardRule(deviceId);

        delFlowRuleByTableId(deviceId, geoForwardTableId);
        delFlowRuleByTableId(deviceId, geoConvTableId);
    }
    public void delIPConversionRule(DeviceId deviceId){
        final String ipConvTableId = "IngressPipeImpl.ipv4_convers_table";
        delFlowRuleByTableId(deviceId, ipConvTableId);
    }

    public void delNdnForwardRule(DeviceId deviceId){
        final String ndnForwardTableId = "IngressPipeImpl.ndn_forward_table";
        final String ndnConvTableId = "IngressPipeImpl.ndn_conversion_table";

        delIPForwardRule(deviceId);
        delIPConversionRule(deviceId);
        delIPPollingForwardRule(deviceId);

        delFlowRuleByTableId(deviceId, ndnForwardTableId);
        delFlowRuleByTableId(deviceId, ndnConvTableId);
    }
    /**
     * Deletes the IP Forward Table for the given device.
     *
     * @param deviceId the device ID
     */
    public void delIPForwardRule(DeviceId deviceId) {
        final String tableId = "IngressPipeImpl.ipv4_forward_table";
        delFlowRuleByTableId(deviceId, tableId);
    }
    public void delIPPollingForwardRule(DeviceId deviceId) {
        final String tableId = "IngressPipeImpl.ipv4_polling_table";
        final String statefulTableId = "IngressPipeImpl.stateful_forward";
        delFlowRuleByTableId(deviceId, tableId);
        delFlowRuleByTableId(deviceId, statefulTableId);
    }


    /**
     * Deletes the given table for the given device.
     *
     * @param deviceId the device ID
     * @param tableId the table ID
     */
    public void delFlowRuleByTableId(DeviceId deviceId, String tableId) {
        List<FlowRule> flows = new ArrayList<>();

        flowRuleService.getFlowEntries(deviceId).forEach(flowEntry -> {
            if (flowEntry.table().toString().equals(tableId)) {
                flows.add(flowEntry);
            }
        });

        if (flows.isEmpty()) {
            log.info("{} has no table {}", deviceId.toString(), tableId);
        } else {
            flows.forEach(f -> flowRuleService.removeFlowRules(f));
        }
    }



    /**
     * Insert flow rules to forward packets to a given host located at the given
     * device and port.
     * <p>
     * This method will be called at component activation for each host known by
     * ONOS, and every time a new host-added event is captured by the
     * InternalHostListener defined below.
     *
     * @param host     host instance
     */
    private void learnHost(Host host) {
        if(hosts.contains(host)){
            log.info("host not need install intent {} ...", host.id());
        }
        // 主机IP转换为MF,geo,ndn
        idConversion(host);

        insertHostToHostIntent(host);
        hosts.add(host);
        // 插入l2_exact_table流表
        deviceService.getAvailableDevices().forEach(device -> {
            if (mastershipService.isLocalMaster(device.id()) && isEdge(device.id()) && device.id().equals(host.location().deviceId()) ) {
                log.info("*** update mac set up for {}...", device.id());
                final Collection<FlowRule> flowRules = host.ipAddresses().stream()
                        .filter(IpAddress::isIp4)
                        .map(IpAddress::getIp4Address)
                        .map(ipv4addr -> buildL2ExactTable(host, device.id(), host.location().port()))
                        .collect(Collectors.toSet());
                installRules(flowRules);
            }
        });
        log.info("Adding host {} ...", host.id());
    }

    private FlowRule buildL2ExactTable(Host host, DeviceId deviceId, PortNumber port) {

        log.info("Adding L2 unicast rule on {} for host {} (port {})...",
                deviceId, host.id(), port);

        // Modify P4Runtime entity names to match content of P4Info file (look
        // for the fully qualified name of tables, match fields, and actions.
        // ---- START SOLUTION ----
        final String tableId = "IngressPipeImpl.l2_exact_table";
        // Match exactly on the host MAC address.
        final MacAddress hostMac = host.mac();
        final PiCriterion hostMacCriterion = PiCriterion.builder()
                .matchExact(PiMatchFieldId.of("hdr.ethernet.dst_addr"),
                        hostMac.toBytes())
                .build();

        // Action: set output port
        final PiAction l2UnicastAction = PiAction.builder()
                .withId(PiActionId.of("IngressPipeImpl.set_egress_port"))
                .withParameter(new PiActionParam(
                        PiActionParamId.of("port"),
                        port.toLong()))
                .build();
        // ---- END SOLUTION ----

        // Forge flow rule.
        final FlowRule rule = Utils.buildFlowRule(
                deviceId, appId, tableId, hostMacCriterion, l2UnicastAction);

        // Insert.
        return rule;
    }

    /**
     * Install the given flow rules in batch using the flow rule service.
     *
     * @param flowRules flow rules to install
     */
    private void installRules(Collection<FlowRule> flowRules) {
        FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
        flowRules.forEach(ops::add);
        flowRuleService.apply(ops.build());
    }

    /**
     * Listener of host events.
     */
    public class InternalHostListener implements HostListener {

        @Override
        public boolean isRelevant(HostEvent event) {
            switch (event.type()) {
                case HOST_ADDED:
                    // Host added events will be generated by the
                    // HostLocationProvider by intercepting ARP/NDP packets.
                    break;
                case HOST_REMOVED:
                case HOST_UPDATED:
                case HOST_MOVED:
                default:
                    // Ignore other events.
                    // Food for thoughts: how to support host moved/removed?
                    return false;
            }
            // Process host event only if this controller instance is the master
            // for the device where this host is attached to.
            final Host host = event.subject();
            final DeviceId deviceId = host.location().deviceId();
            return mastershipService.isLocalMaster(deviceId);
        }

        @Override
        public void event(HostEvent event) {
            final Host host = event.subject();
            // Device and port where the host is located.
            final DeviceId deviceId = host.location().deviceId();
            final PortNumber port = host.location().port();
//            log.info("111111111111111111");
//            log.info("port is {}",port);
//            log.info("isEdge(deviceId) is {}",isEdge(deviceId));
//            log.info("port.toString().equals(1) is {}",port.toString().equals(1));
//            log.info("port.toString().equals(2) is {}",port.toString().equals(2));
//            if (isEdge(deviceId) && (port.toString().equals(1)||port.toString().equals(2))){
                mainComponent.getExecutorService().execute(() -> {
                    log.info("{} event! host={}, deviceId={}, port={}",
                            event.type(), host.id(), deviceId, port);
                    learnHost(host);
                });
//            }
//            mainComponent.getExecutorService().execute(() -> {
//                log.info("{} event! host={}, deviceId={}, port={}",
//                        event.type(), host.id(), deviceId, port);
//
//                learnHost(host);
//            });
        }
    }

    /**
     * Listener of link events, which triggers configuration of routing rules to
     * forward packets across the fabric, i.e. from leaves to spines and vice
     * versa.
     * <p>
     * Reacting to link events instead of device ones, allows us to make sure
     * all device are always configured with a topology view that includes all
     * links, e.g. modifying an ECMP group as soon as a new link is added. The
     * downside is that we might be configuring the same device twice for the
     * same set of links/paths. However, the ONOS core treats these cases as a
     * no-op when the device is already configured with the desired forwarding
     * state (i.e. flows and groups)
     */
    class InternalLinkListener implements LinkListener {

        @Override
        public void event(LinkEvent event) {
            //log.info("{} event!...", event.type());
            DeviceId srcDev = event.subject().src().deviceId();
            DeviceId dstDev = event.subject().dst().deviceId();

            if (mastershipService.isLocalMaster(srcDev) && (mastershipService.isLocalMaster(dstDev))) {
                if (event.type() == LinkEvent.Type.LINK_REMOVED ) {
                    mainComponent.getExecutorService().execute(() -> {
                        log.info("{} event!... linkSrc={}, linkDst={}", event.type(),  srcDev, dstDev);
                        Link l1 = event.subject();
                        for(HostToHosLinks hLink:hostToHostLinks){
                            if(hLink.isPathLinkChange(l1)){
                                updateHostToHostPath(hLink.getSrcHost(), hLink.getDstHost(), l1);
                            }
                        }
                    });
                }
            }

        }
    }
    /**
     * Sets up L2 bridging on all devices known by ONOS and for which this ONOS
     * node instance is currently master.
     * <p>
     * This method is called at component activation.
     */
    private void setUpAllDevices() {
        deviceService.getAvailableDevices().forEach(device -> {
            if (mastershipService.isLocalMaster(device.id())) {
                log.info("*** L2 BRIDGING - Starting initial set up for {}...", device.id());
                hostService.getConnectedHosts(device.id()).forEach(
                        host -> learnHost(host));
            }
        });
    }
}
